home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 9675 / 9675.xpi / chrome / content / simpletimer-countdown.js < prev    next >
Text File  |  2009-11-17  |  23KB  |  586 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Simple Timer.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * George Bradt.
  18.  
  19.  * Portions created by the Initial Developer are Copyright (C) 2009
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. var SimpleTimerCountdown = {
  39.     // For reference, not used
  40.     countdownTimer: {timeDisplay: "",
  41.                      recurring: false,
  42.                      description: "",
  43.                      timeMilli: 0,
  44.                      timeFinishedMilli: 0,
  45.                      paused: false
  46.                     },
  47.     countdownSep: "\x1D", // used to separate countdowns in pref.
  48.  
  49.     // Called when loading Countdown time entry dialog.
  50.  
  51.     onLoadCountdown: function() {
  52.         var strbundle = document.getElementById("simtim-strings");
  53.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  54.                 getService(Components.interfaces.nsIPrefService).
  55.                 getBranch("extensions.simpletimer@grbradt.org.");
  56.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  57.                 getService(Components.interfaces.fuelIApplication);
  58.         var countdownTable = [];
  59.  
  60.         document.getElementById("simtim-tboxCountdownHours").value =
  61.                 prefs.getIntPref("countdownHours");
  62.         document.getElementById("simtim-tboxCountdownMinutes").value =
  63.                 prefs.getIntPref("countdownMinutes");
  64.         document.getElementById("simtim-tboxCountdownSeconds").value =
  65.                 prefs.getIntPref("countdownSeconds");
  66.  
  67.         if ( Application.storage.has("countdownTable") ) {
  68.             countdownTable = Application.storage.get("countdownTable", "ERR");
  69.  
  70.             if ( countdownTable === "ERR" ) {
  71.                 alert(strbundle.getString("alert.error.loading.countdowns"));
  72.                 return;
  73.             }
  74.         }
  75.         else {
  76.             alert(strbundle.getString("alert.error.loading.countdowns"));
  77.             return;
  78.         }
  79.  
  80.         var treeChildren = document.getElementById("simtim-treeItems");
  81.  
  82.         // Clear the old tree data.
  83.         this.onDeleteAllCountdown();
  84.  
  85.         for ( var i in countdownTable ) {
  86.             this.addTreeRow(treeChildren, countdownTable[i]);
  87.         }
  88.     },
  89.  
  90.     // Called when Add/OK button is clicked.
  91.  
  92.     onAddOkCountdown: function() {
  93.         if ( this.onAddCountdown() ) {
  94.             this.onOKCountdown();
  95.             window.close();
  96.         }
  97.     },
  98.  
  99.     // Called when Add button is clicked.
  100.  
  101.     onAddCountdown: function() {
  102.         var strbundle = document.getElementById("simtim-strings");
  103.         var hours = document.getElementById("simtim-tboxCountdownHours").value;
  104.         var minutes = document.getElementById("simtim-tboxCountdownMinutes").value;
  105.         var seconds = document.getElementById("simtim-tboxCountdownSeconds").value;
  106.  
  107.         if ( hours === "0" &&
  108.              minutes === "0" &&
  109.              seconds === "0" ) {
  110.             alert(strbundle.getString("alert.warning.nonzero.user"));
  111.             return false;
  112.         }
  113.  
  114.         var timeDisplay = this.padTime(hours) + ":" + this.padTime(minutes) + ":" + this.padTime(seconds);
  115.         var timeMilli = this.convertTimeToMilli(hours, minutes, seconds);
  116.         var descr = document.getElementById("simtim-tboxCountdownDescr").value;
  117.         var recurring = document.getElementById("simtim-cboxCountdownRecurring").checked;
  118.         var tree = document.getElementById("simtim-treeCountdown");
  119.         var boxobject = tree.boxObject;
  120.         boxobject.QueryInterface(Components.interfaces.nsITreeBoxObject);
  121.         var treeChildren = document.getElementById("simtim-treeItems");
  122.         var treeItems = treeChildren.hasChildNodes() ? treeChildren.childNodes: null;
  123.  
  124.         var data = {timeDisplay: timeDisplay, recurring: recurring, description: descr,
  125.                     timeMilli: timeMilli, timeFinishedMilli: 0, paused: false};
  126.  
  127.         this.addTreeRow(treeChildren, data);
  128.  
  129.         // Select the added row.
  130.         var rowIndex = treeItems ? treeItems.length - 1 : 0;
  131.         tree.view.selection.select(rowIndex);
  132.  
  133.         // Ensure the added/updated row is visible, tree displays 5 rows (unless dlg stretched).
  134.         if ( treeItems && treeItems.length > 5 ) {
  135.             boxobject.ensureRowIsVisible(rowIndex);
  136.         }
  137.  
  138.         // Clear the description textbox.
  139.         document.getElementById("simtim-tboxCountdownDescr").value = "";
  140.  
  141.         return true;
  142.     },
  143.  
  144.     // Add a timer to the tree.
  145.  
  146.     addTreeRow: function(treeChildren, data) {
  147.         var item = document.createElement("treeitem");
  148.         var row = document.createElement("treerow");
  149.         var cell;
  150.  
  151.         // First cell is "timeDisplay", not editable.
  152.         cell = document.createElement("treecell");
  153.         cell.setAttribute("label", data.timeDisplay);
  154.         cell.setAttribute("value", data.timeMilli);
  155.         cell.setAttribute("timeFinishedMilli", data.timeFinishedMilli);
  156.         cell.setAttribute("editable", "false");
  157.         row.appendChild(cell);
  158.  
  159.         // Second cell is "recurring", displayed as a checkbox.
  160.         cell = document.createElement("treecell");
  161.         cell.setAttribute("value", data.recurring);
  162.         row.appendChild(cell);
  163.  
  164.         // Third cell is "descr", the optional description. Stash paused.
  165.         cell = document.createElement("treecell");
  166.         cell.setAttribute("label", data.description);
  167.         cell.setAttribute("value", data.paused);
  168.         row.appendChild(cell);
  169.  
  170.         // Add the treerow onto treeitem, append item.
  171.         item.appendChild(row);
  172.         treeChildren.appendChild(item);
  173.     },
  174.  
  175.     // Called when Delete button is clicked.
  176.  
  177.     onDeleteCountdown: function() {
  178.         var strbundle = document.getElementById("simtim-strings");
  179.         var tree = document.getElementById("simtim-treeCountdown");
  180.  
  181.         if ( tree.currentIndex < 0 ) {
  182.             // User didn't make a selection.
  183.             alert(strbundle.getString("alert.warning.no.item.selected"));
  184.         }
  185.         else {
  186.             var start = {};
  187.             var end = {};
  188.             var treeItem;
  189.             var numRanges = tree.view.selection.getRangeCount();
  190.  
  191.             // Start with the last selection range and work up.
  192.             for ( var i = numRanges - 1; i >= 0; i-- ) {
  193.                 tree.view.selection.getRangeAt(i,start,end);
  194.  
  195.                 // Within a range, delete from the bottom up.
  196.                 for ( var j = end.value; j >= start.value; j-- ) {
  197.                     treeItem = tree.view.getItemAtIndex(j);
  198.                     treeItem.parentNode.removeChild(treeItem);
  199.                 }
  200.             }
  201.         }
  202.     },
  203.  
  204.     // Called when Del All button is clicked.
  205.  
  206.     onDeleteAllCountdown: function() {
  207.         var treeChildren = document.getElementById("simtim-treeItems");
  208.  
  209.         // Remove from the bottom.
  210.         while ( treeChildren.hasChildNodes() ) {
  211.             treeChildren.removeChild(treeChildren.lastChild);
  212.         }
  213.     },
  214.  
  215.     // Save the favourite timers list in a pref.
  216.     // Called when Save List button is clicked.
  217.  
  218.     onSaveList: function() {
  219.         var strbundle = document.getElementById("simtim-strings");
  220.         var str = Components.classes["@mozilla.org/supports-string;1"].
  221.                 createInstance(Components.interfaces.nsISupportsString);
  222.  
  223.         // Objects stored as JSON strings in the pref.
  224.         var json = Components.classes["@mozilla.org/dom/json;1"].
  225.                 createInstance(Components.interfaces.nsIJSON);
  226.  
  227.         var treeChildren = document.getElementById("simtim-treeItems");
  228.  
  229.         if ( treeChildren.hasChildNodes() ) {
  230.             // Get confirmation.
  231.             var button = confirm(strbundle.getString("confirm.save.timer.list"));
  232.  
  233.             if ( !button ) {
  234.                 // User didn't click OK.
  235.                 return;
  236.             }
  237.  
  238.             // Build pref string of all timers from dialog Countdown timers tree.
  239.             var row, timeDisplay, recurring, description, paused,
  240.                 timeMilli, timeFinishedMilli, timeNode;
  241.             var prefString = "";
  242.             var currTimeMilli = new Date().getTime();
  243.  
  244.             var treeItems = treeChildren.childNodes;
  245.             var len = treeItems.length;
  246.  
  247.             for ( var i = 0; i < len; i++ ) {
  248.                 row = treeItems[i].firstChild;
  249.                 timeNode = row.firstChild;
  250.                 timeDisplay = timeNode.getAttribute("label");
  251.                 timeMilli = parseInt(timeNode.getAttribute("value"), 10);
  252.  
  253.                 // Convert string attribute to boolean.
  254.                 recurring = ( row.firstChild.nextSibling.getAttribute("value") === "true" ) ?
  255.                         true : false;
  256.  
  257.                 description = row.lastChild.getAttribute("label");
  258.  
  259.                 try {
  260.                     prefString += json.encode( {timeDisplay: timeDisplay, recurring: recurring,
  261.                             description: description, timeMilli: timeMilli, timeFinishedMilli: 0,
  262.                             paused: false} ) + this.countdownSep;
  263.                 }
  264.                 catch(e) {
  265.                     alert(strbundle.getString("alert.error.encoding.timer"));
  266.                     return;
  267.                 }
  268.             }
  269.  
  270.             // Remove last separator.
  271.             if ( prefString ) {
  272.                 prefString = prefString.substring(0, prefString.length - 1);
  273.             }
  274.  
  275.             str.data = prefString;
  276.             window.opener.SimpleTimer.prefs.setComplexValue("favCountdowns",
  277.                     Components.interfaces.nsISupportsString, str);
  278.         }
  279.         else {
  280.             alert(strbundle.getString("msg.no.timers"));
  281.         }
  282.     },
  283.  
  284.     // Load the favourite timers list from pref.
  285.     // Called when Load List button is clicked.
  286.  
  287.     onLoadList: function() {
  288.         var strbundle = document.getElementById("simtim-strings");
  289.  
  290.         if ( !window.opener.SimpleTimer.favCountdowns ) {
  291.             alert(strbundle.getString("msg.timers.no.list"));
  292.             return;
  293.         }
  294.  
  295.         // Objects stored as JSON strings in the pref.
  296.         var json = Components.classes["@mozilla.org/dom/json;1"].
  297.                 createInstance(Components.interfaces.nsIJSON);
  298.  
  299.         var favCountdownArray = window.opener.SimpleTimer.favCountdowns.split(this.countdownSep);
  300.         var treeChildren = document.getElementById("simtim-treeItems");
  301.         var tempArray = [];
  302.  
  303.         for ( var i in favCountdownArray ) {
  304.             try {
  305.                 tempArray[i] = json.decode(favCountdownArray[i]);
  306.             }
  307.             catch(e) {
  308.                 alert(strbundle.getString("alert.error.decoding.timer"));
  309.                 return;
  310.             }
  311.  
  312.             this.addTreeRow(treeChildren, tempArray[i]);
  313.         }
  314.     },
  315.  
  316.     // Pad a display time with zero if necessary.
  317.  
  318.     padTime: function(time) {
  319.         return ( ( time < 10 ) ? "0" + time : time );
  320.     },
  321.  
  322.     // Convert a time to milliseconds.
  323.  
  324.     convertTimeToMilli: function(hrs, mins, secs) {
  325.         return  ( ( hrs * 60 * 60 * 1000 ) + ( mins * 60 * 1000 ) + ( secs * 1000 ) );
  326.     },
  327.  
  328.     // Called when user clicks OK.
  329.  
  330.     onOKCountdown: function() {
  331.         window.opener.SimpleTimer.prefs.setIntPref("countdownHours",
  332.                 document.getElementById("simtim-tboxCountdownHours").value);
  333.         window.opener.SimpleTimer.prefs.setIntPref("countdownMinutes",
  334.                 document.getElementById("simtim-tboxCountdownMinutes").value);
  335.         window.opener.SimpleTimer.prefs.setIntPref("countdownSeconds",
  336.                 document.getElementById("simtim-tboxCountdownSeconds").value);
  337.  
  338.         this.buildCountdownTable();
  339.  
  340.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  341.         var wmed = wm.QueryInterface(Components.interfaces.nsIWindowMediator);
  342.         var enumerator = wmed.getEnumerator("navigator:browser");
  343.  
  344.         while ( enumerator.hasMoreElements() ) {
  345.             var win = enumerator.getNext();
  346.  
  347.             win.SimpleTimer.cancelTimerInProgress();
  348.  
  349.             if ( win.SimpleTimer.getCountdownTable() ) {
  350.                 win.SimpleTimer.setCountdownTimer();
  351.             }
  352.         }
  353.     },
  354.  
  355.     // Construct an array of countdown timer objects.
  356.     // Called when the user clicks OK or Add/OK.
  357.  
  358.     buildCountdownTable: function() {
  359.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  360.                 getService(Components.interfaces.fuelIApplication);
  361.  
  362.         var treeChildren = document.getElementById("simtim-treeItems");
  363.         var countdownTable = [];
  364.  
  365.         if ( treeChildren.hasChildNodes() ) {
  366.             // Build table of all timers from dialog Countdown timers tree.
  367.             var row, timeDisplay, recurring, description, paused,
  368.                 timeMilli, timeFinishedMilli, timeNode;
  369.             var currTimeMilli = new Date().getTime();
  370.  
  371.             var treeItems = treeChildren.childNodes;
  372.             var len = treeItems.length;
  373.  
  374.             for ( var i = 0; i < len; i++ ) {
  375.                 row = treeItems[i].firstChild;
  376.                 timeNode = row.firstChild;
  377.                 timeDisplay = timeNode.getAttribute("label");
  378.                 timeMilli = parseInt(timeNode.getAttribute("value"), 10);
  379.                 timeFinishedMilli = parseInt(timeNode.getAttribute("timeFinishedMilli"), 10);
  380.  
  381.                 // Convert string attribute to boolean.
  382.                 recurring = ( row.firstChild.nextSibling.getAttribute("value") === "true" ) ?
  383.                         true : false;
  384.  
  385.                 // Check if an existing timer expired while dialog open.
  386.                 if ( timeFinishedMilli > 0 && currTimeMilli > timeFinishedMilli ) {
  387.                     if ( recurring ) {
  388.                         // Cannot use timeMilli, since it changes if paused.
  389.                         var timeDisplayMilli = SimpleTimer.convertClockToMilli(timeDisplay);
  390.  
  391.                         timeFinishedMilli += timeDisplayMilli *
  392.                                 Math.ceil((currTimeMilli - timeFinishedMilli) / timeDisplayMilli);
  393.                     }
  394.                     else {
  395.                         continue;
  396.                     }
  397.                 }
  398.  
  399.                 description = row.lastChild.getAttribute("label");
  400.                 paused = ( row.lastChild.getAttribute("value") === "true" ) ?
  401.                         true : false;
  402.  
  403.                 // Don't use i for index, as some tree items may be bypassed.
  404.                 countdownTable[countdownTable.length] = {timeDisplay: timeDisplay,
  405.                                                          recurring: recurring,
  406.                                                          description: description,
  407.                                                          timeMilli: timeMilli,
  408.                                                          timeFinishedMilli: timeFinishedMilli,
  409.                                                          paused: paused
  410.                                                         };
  411.             }
  412.         }
  413.  
  414.         Application.storage.set("countdownTable", countdownTable);
  415.     },
  416.  
  417.     // Construct an array of countdown objects from the countdowns pref,
  418.     // and store via FUEL. Called at startup.
  419.  
  420.     buildCountdownTableFromPref: function(countdowns) {
  421.         var strbundle = document.getElementById("simtim-strings");
  422.         var currTimeMilli = new Date().getTime();
  423.  
  424.         // Objects stored as JSON strings in the pref.
  425.         var json = Components.classes["@mozilla.org/dom/json;1"].
  426.                 createInstance(Components.interfaces.nsIJSON);
  427.  
  428.         var eventDescription, eventRecurring;
  429.         var tempArray = [];
  430.         var expiredArray = [];
  431.         var countdownTable = [];
  432.         var countdownArray = countdowns.split(this.countdownSep);
  433.  
  434.         for ( var i in countdownArray ) {
  435.             tempArray[i] = json.decode(countdownArray[i]);
  436.  
  437.             if ( !tempArray[i].hasOwnProperty('paused') ) {
  438.                 tempArray[i].paused = false;
  439.             }
  440.  
  441.             // Exclude timers that expired while browser closed. Always include recurring,
  442.             // adjusting its finish time if necessary.
  443.             if ( tempArray[i].recurring && currTimeMilli >= tempArray[i].timeFinishedMilli ) {
  444.                 tempArray[i].timeFinishedMilli +=
  445.                         tempArray[i].timeMilli *
  446.                         Math.ceil((currTimeMilli - tempArray[i].timeFinishedMilli) / tempArray[i].timeMilli);
  447.             }
  448.  
  449.             // Paused timer will get a valid finish time only when resumed.
  450.             // This will sort to the top.
  451.             if ( tempArray[i].paused ) {
  452.                 tempArray[i].timeFinishedMilli = 0;
  453.             }
  454.  
  455.             if ( currTimeMilli <= tempArray[i].timeFinishedMilli ||
  456.                  tempArray[i].paused ) {
  457.                 countdownTable[countdownTable.length] = tempArray[i];
  458.             }
  459.             else {
  460.                 // Expired, save for display in slider alert.
  461.                 expiredArray.push({timeDisplay: tempArray[i].timeDisplay,
  462.                                    description: tempArray[i].description});
  463.  
  464.                 if ( SimpleTimer.eventLogging ) {
  465.                     eventDescription = ( tempArray[i].description ) ?
  466.                                     tempArray[i].description :
  467.                                     strbundle.getString("msg.none");
  468.  
  469.                     eventRecurring = ( tempArray[i].recurring ) ?
  470.                                   strbundle.getString("msg.yes") :
  471.                                   strbundle.getString("msg.no");
  472.  
  473.                     // Pass event type, event time, recurring, status, description, URL(N/A).
  474.                     SimpleTimerEventLog.logEvent(strbundle.getString("msg.countdown"),
  475.                                                   tempArray[i].timeDisplay,
  476.                                                   eventRecurring,
  477.                                                   strbundle.getString("msg.expired"),
  478.                                                   eventDescription,
  479.                                                   strbundle.getString("msg.not.applicable"));
  480.                 }
  481.             }
  482.         }
  483.  
  484.         // If opening another browser window, user would have seen the expired alerts.
  485.         if ( !SimpleTimer.otherBrowserWindowOpen() ) {
  486.             if ( countdownTable.length === 0 ) {
  487.             var params = { displayItems: expiredArray, msg: strbundle.getString("msg.countdown.timers.none" )};
  488.             setTimeout(SimpleTimerSliderAlert.addMessageToQueue, 5000, params);
  489.             setTimeout("SimpleTimer.playSound(3)", 5000);
  490.             }
  491.             else if (countdownTable.length !== countdownArray.length ) {
  492.                 params = { displayItems: expiredArray, msg: strbundle.getString("msg.countdown.timers.some" )};
  493.                 setTimeout(SimpleTimerSliderAlert.addMessageToQueue, 5000, params);
  494.                 setTimeout("SimpleTimer.playSound(3)", 5000);
  495.             }
  496.             else {
  497.                 params = { displayItems: null, msg: strbundle.getString("msg.countdown.timers.all" )};
  498.                 setTimeout(SimpleTimerSliderAlert.addMessageToQueue, 5000, params);
  499.                 setTimeout("SimpleTimer.playSound(3)", 5000);
  500.             }
  501.         }
  502.  
  503.         Application.storage.set("countdownTable", countdownTable);
  504.  
  505.         if ( countdownTable.length !== countdownArray.length ) {
  506.             // Update pref in case another window opened.
  507.             this.updateCountdownPref();
  508.         }
  509.     },
  510.  
  511.     // Write countdowns pref from countdown table. Called from SimpleTimer.
  512.  
  513.     updateCountdownPref: function() {
  514.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  515.                 getService(Components.interfaces.nsIPrefService).
  516.                 getBranch("extensions.simpletimer@grbradt.org.");
  517.  
  518.         // For Unicode.
  519.         var str = Components.classes["@mozilla.org/supports-string;1"].
  520.                 createInstance(Components.interfaces.nsISupportsString);
  521.  
  522.         var Application = Components.classes["@mozilla.org/fuel/application;1"].
  523.                 getService(Components.interfaces.fuelIApplication);
  524.  
  525.         var countdownTable = [];
  526.  
  527.         if ( Application.storage.has("countdownTable") ) {
  528.             countdownTable = Application.storage.get("countdownTable", "ERR");
  529.  
  530.             if ( countdownTable === "ERR" ) {
  531.                 alert(strbundle.getString("alert.error.loading.countdowns"));
  532.                 return;
  533.             }
  534.         }
  535.         else {
  536.             alert(strbundle.getString("alert.error.loading.countdowns"));
  537.             return;
  538.         }
  539.  
  540.         // Objects stored as JSON strings in the pref.
  541.         var json = Components.classes["@mozilla.org/dom/json;1"].
  542.                 createInstance(Components.interfaces.nsIJSON);
  543.  
  544.         var prefString = "";
  545.  
  546.         for ( var i in countdownTable ) {
  547.             prefString += json.encode(countdownTable[i]) + this.countdownSep;
  548.         }
  549.  
  550.         // Remove last separator.
  551.         if ( prefString ) {
  552.             prefString = prefString.substring(0, prefString.length - 1);
  553.         }
  554.  
  555.         str.data = prefString;
  556.         SimpleTimer.prefs.setComplexValue("countdowns",
  557.                 Components.interfaces.nsISupportsString, str);
  558.     },
  559.  
  560.     // Sort the countdown table on objects timeFinishedMilli property.
  561.     // Called from SimpleTimer.setCountdownTimer().
  562.  
  563.     sortCountdownTable: function(a, b) {
  564.         var x = a.timeFinishedMilli;
  565.         var y = b.timeFinishedMilli;
  566.  
  567.         return ( (x < y) ? -1 : ( (x > y) ? 1 : 0 ) );
  568.     },
  569.  
  570.     // Dump to console.
  571.  
  572.     dumpCountdownObject: function (countdownTimer) {
  573.         this.debug("\nCountdown timer data:");
  574.         this.debug("Countdown Time for Display: " + countdownTimer.timeDisplay);
  575.         this.debug("Recurring?: " + countdownTimer.recurring);
  576.         this.debug("Description: " + countdownTimer.description);
  577.         this.debug("Countdown time (millisecs): " + countdownTimer.timeMilli);
  578.         this.debug("Countdown finish time (millisecs): " + countdownTimer.timeFinishedMilli);
  579.     },
  580.  
  581.     // Debug messages to console.
  582.  
  583.     debug: function (aMsg) {
  584.         setTimeout(function() { throw new Error("[debug] " + aMsg); }, 0);
  585.     }
  586. };